Android中使用x5内核加载网页的实现 您所在的位置:网站首页 android 访问网页 Android中使用x5内核加载网页的实现

Android中使用x5内核加载网页的实现

2023-12-14 14:49| 来源: 网络整理| 查看: 265

前言联系方式背景SDK下载SDK集成使用代码实现

前言

由于是使用的腾讯浏览服务,所以这里大部分介绍的是官网的一些东西,不过自己会做一些复杂使用部分的实现,不至于像官网上介绍的笼统。

联系方式

这里用的是TBS腾讯浏览服务。

官网地址是 https://x5.tencent.com

微信公众号:腾讯浏览服务

论坛 http://bbs.mb.qq.com/forum-112-1.html ( 开发者反馈论坛 )

反馈 http://bbs.mb.qq.com/newthread?fid=112

背景

这里介绍一下x5内核相对Android系统内核的一些优势

TBS(腾讯浏览服务)的优势

1) 速度快:相比系统webview的网页打开速度有30+%的提升; 2) 省流量:使用云端优化技术使流量节省20+%; 3) 更安全:安全问题可以在24小时内修复; 4) 更稳定:经过亿级用户的使用考验,CRASH率低于0.15%; 5) 兼容好:无系统内核的碎片化问题,更少的兼容性问题; 6) 体验优:支持夜间模式、适屏排版、字体设置等浏览增强功能; 7) 功能全:在Html5、ES6上有更完整支持; 8) 更强大:集成强大的视频播放器,支持视频格式远多于系统webview; 9) 视频和文件格式的支持x5内核多于系统内核; 10) 防劫持是x5内核的一大亮点

运行环境

1)手机ROM版本高于或等于2.2版本; 2)手机RAM大于500M,该RAM值通过手机 /proc/meminfo 文件的MemTotal动态获取 注:如果不满足上述条件,SDK会自动切换到系统WebView,SDK使用者不用关心该切换过程。

SDK尺寸指标

1)SDK提供的JAR包约250K

SDK下载

我们肯定需要先下载sdk的jar包,然后才能使用该sdk的功能,也就是这里的x5内核。

腾讯浏览服务的sdk下载链接是:腾讯SDK下载页。这里我选择的是第三个(完整版+文件能力的Android SDK),当然选择第一个也可以,只不过不带有文件能力。第二个适用于快速接入TBS且常规使用WebView的开发者。因为我这里可能还涉及到与html网页的交互,为了方便,就没有选择第二个。因为第一个和第三个都只是单纯的将x5的内核替换系统的内核,其他没有太多的变化。像一些基本的设置也都和系统内核的是一样的,只不过x5的内核优势很明显。

SDK集成

sdk下载完成后,解压如下图所示: 这里写图片描述

里面有文件常见问题解答文档和文件方案接口介绍文档。因为是带有文件能力的sdk,所以有这些文档,文档也比较简单,写的很基础,这里不多介绍了。

这里我们用到的是jar包,我标注在图中了。将jar包添加到我们的项目中,就可以使用对应的功能了。

将jar包放到libs目录下: 这里写图片描述

然后记得添加jar包依赖,如下: 这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

经过上面几个步骤之后,你的项目就集成了该sdk,可以使用x5的内核了。

使用

用到的所有原生WebView导入的类和接口都改导入 com.tencent.smtt.sdk 里面的类,类接口名对应。这样就相当于用x5的内核替换了系统的内核。

需要注意的是:

1)请不要在代码里使用下述写法

import android.*; import android.webkit.*; import android.webkit.WebStorage.*; import android.net.*; import android.net.http.*;

2)除了源码里需要把相关的包名和类名进行替换,布局xml里面的声明也需要替换,例如:

当然,你要是不在布局里面声明,而是自己在代码中new出来,添加的x5内核,那么就不用管xml布局。这里推荐大家用这种方法来实现,会比在xml布局中声明更加的灵活方便。

替换不完全时,可能发生的问题是关于cookie的身份错误、类转换时的crash等。cookie问题产生的原理是:一段代码把cookie塞给了系统内核,另外一段代码尝试从x5的内核里读取cookie就失败了。类转换的错误产生的原理是:比如xml里指定的是系统的webview,java的代码里把它当作x5的webview使用。

然后大家自己原来已经用的系统的内核,再替换为x5的内核需要你仔细一点,别忘记替换包名,不然就会发生上面的错误。如果刚开始就使用的就是集成的x5内核,那么就不用太担心,不过选择导包的时候还是需要注意别用系统的内核,要使用腾讯提供的x5内核。

x5暂时不提供64位so文件,为了保证64位手机能正常加载x5内核,请参照如下链接修改相关配置https://x5.tencent.com/tbs/technical.html#/detail/sdk/1/34cf1488-7dc2-41ca-a77f-0014112bcab7

AndroidManifest.xml里加入权限声明:

优化异常上报:

为了提高合作方的webview场景稳定性,及时发现并解决x5相关问题,当客户端发生crash等异常情况并上报给服务器时请务必带上x5内核相关信息。x5内核异常信息获取接口为:com.tencent.smtt.sdk.WebView.getCrashExtraMessage(context)。以bugly日志上报为例:

UserStrategy strategy = new UserStrategy(appContext);   strategy.setCrashHandleCallback(new CrashReport.CrashHandleCallback() {     public Map onCrashHandleStart(int crashType, String errorType, String errorMessage, String errorStack) {       LinkedHashMap map = new LinkedHashMap();       String x5CrashInfo = com.tencent.smtt.sdk.WebView.getCrashExtraMessage(appContext);       map.put("x5crashInfo", x5CrashInfo);       return map;     }     @Override     public byte[] onCrashHandleStart2GetExtraDatas(int crashType, String errorType, String errorMessage, String errorStack) {       try {         return "Extra data.".getBytes("UTF-8");       } catch (Exception e) {         return null;       }     }   });   CrashReport.initCrashReport(appContext, APPID, true, strategy);

适配修改:

1) App 首次就可以加载 x5 内核

App 在启动后(例如在 Application 的 onCreate 中)立刻调用 QbSdk 的预加载接口 initX5Environment ,可参考接入示例,第一个参数传入 context,第二个参数传入 callback,不需要 callback 的可以传入 null,initX5Environment 内部会创建一个线程向后台查询当前可用内核版本号,这个函数内是异步执行所以不会阻塞 App 主线程,这个函数内是轻量级执行所以对 App 启动性能没有影响,当 App 后续创建 webview 时就可以首次加载 x5 内核了

package com.example.administrator.firststeppro.application; import android.app.Application; import com.tencent.smtt.sdk.QbSdk; /** * 自定义MyApplication类继承Application * 并重写onCreate方法完成一些初始化加载操作 */ public class MyApplication extends Application{ @Override public void onCreate() { super.onCreate(); preinitX5WebCore(); } /** * 预加载x5内核 */ private void preinitX5WebCore() { if (!QbSdk.isTbsCoreInited()){ // 这个函数内是异步执行所以不会阻塞 App 主线程,这个函数内是轻量级执行所以对 App 启动性能没有影响 QbSdk.initX5Environment(this, null); } } }

别忘记在清单配置文件中声明该Application:

设置name属性即可。

2) 目前,由于SDK WebView所提供的WebView类,是对系统WebView的聚合包装,所以:获取系统内核的WebView或者 x5内核的WebView的宽高

android.webkit.WebView webView = new android.webkit.WebView(this); int width = webView.getWidth();

需要采用下面的方式进行

com.tencent.smtt.sdk.WebView webView = new com.tencent.smtt.sdk.WebView(this); int width = webView.getView().getWidth();

调整cookie的使用:

com.tencent.smtt.sdk.CookieManager和com.tencent.smtt.sdk.CookieSyncManager的相关接口的调用,在接入SDK后,需要放到创建X5的WebView之后(也就是X5内核加载完成)进行;否则,cookie的相关操作只能影响系统内核。

兼容视频播放:

1)享受页面视频的完整播放体验需要做如下声明:

页面的Activity需要声明android:configChanges="orientation|screenSize|keyboardHidden"

2)视频为了避免闪屏和透明问题,需要如下设置

a)网页中的视频,上屏幕的时候,可能出现闪烁的情况,需要如下设置:Activity在onCreate时需要设置:

getWindow().setFormat(PixelFormat.TRANSLUCENT);(这个对宿主没什么影响,建议声明)

b)在非硬绘手机和声明需要controller的网页上,视频切换全屏和全屏切换回页面内会出现视频窗口透明问题,需要如下设置

声明当前false为不透明。 特别说明:这个视各app情况所需,不强制需求,如果声明了,对体验更有利

c)以下接口禁止(直接或反射)调用,避免视频画面无法显示:

webview.setLayerType() webview.setDrawingCacheEnabled(true);

输入法设置

避免输入法界面弹出后遮挡输入光标的问题

方法一:在AndroidManifest.xml中设置

android:windowSoftInputMode="stateHidden|adjustResize"

方法二:在代码中动态设置:

getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);

app 自定义 UA 的说明

如果 app 需要自定义 UA,建议采取在 SDK 默认UA 后追加 app UA 的方式示例:

webSetting.setUserAgentString(webSetting.getUserAgentString() + APP_NAME_UA); 其中 APP_NAME_UA 是 app 自定义 UA

由于我们提供的 TBS jar 已经混淆过,所以 App 混淆时可以不再混淆我们的 TBS jar

代码实现

下面我给出我自己的项目中使用x5内核加载的网页,顺便也测试一下是否成功的加载了x5内核。

package com.example.administrator.firststeppro.fragment; import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.PixelFormat; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.app.Fragment; import android.support.annotation.NonNull; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.tencent.smtt.export.external.extension.interfaces.IX5WebViewExtension; import com.tencent.smtt.export.external.interfaces.SslError; import com.tencent.smtt.export.external.interfaces.SslErrorHandler; import com.tencent.smtt.sdk.WebChromeClient; import com.tencent.smtt.export.external.interfaces.WebResourceResponse; import com.tencent.smtt.sdk.WebSettings; import com.tencent.smtt.sdk.WebView; import com.tencent.smtt.sdk.WebViewClient; import android.widget.LinearLayout; import android.widget.Toast; import com.example.administrator.firststeppro.R; import com.example.administrator.firststeppro.base.BaseFragment; import com.example.administrator.firststeppro.orm.AndroidToJs; import com.example.administrator.firststeppro.utils.LogUtil; import com.example.administrator.firststeppro.utils.ToastUtil; import butterknife.BindView; import butterknife.ButterKnife; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; /** * A simple {@link Fragment} subclass. * 展示个人的csdn博客主页 */ public class PageOneFragment extends BaseFragment { @BindView(R.id.linear_page1_webview)LinearLayout linear_page1_webiew;// webView的父布局 @BindView(R.id.material_progress_bar)MaterialProgressBar material_progress_bar; private boolean isPrepared = false;// 是否已经准备好要加载数据 private static WebView webView = null; private WebSettings webSettings;// WebSettings对象 public PageOneFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_page_one, container, false); getActivity().getWindow().setFormat(PixelFormat.TRANSLUCENT);// 加载网页视频避免闪屏和透明 ButterKnife.bind(this, view); isPrepared = true; setLazyLoad(); requestPermission();// 申请权限 return view; } @SuppressLint("NewApi") private void requestPermission() { if(getContext().checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){ // 进行授权 requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode){ case 1: if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ }else { ToastUtil.showToast(getContext(), "读写文件权限未开启"); } break; } } /* @OnClick({R.id.btn_callJs}) public void doClick(View view){ switch (view.getId()){ case R.id.btn_callJs:// Android调用js方法 webView.post(new Runnable() { @Override public void run() { // 调用index.html文件的callJS()方法 // webView.loadUrl("javascript:callJS()"); // 不同版本兼容4.4以上 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { webView.evaluateJavascript("index:callJS()", new ValueCallback() { @Override public void onReceiveValue(String value) { // 此处为js返回的结果 LogUtil.e("TAG","" + value); } }); }else { webView.loadUrl("index:callJS()"); } } }); break; } } */ @Override protected void setLazyLoad() { super.setLazyLoad(); if (isVisible && isPrepared){ LogUtil.e("TAG","loadDataPage1"); if (webView == null){ openWebViewPage(); } } } /** * 打开webView页面 */ private void openWebViewPage() { // 创建WebView对象添加到布局中 webView = new WebView(getContext().getApplicationContext()); IX5WebViewExtension ix5WebViewExtension = webView.getX5WebViewExtension(); ix5WebViewExtension.setScrollBarFadingEnabled(false); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); webView.setLayoutParams(params); linear_page1_webiew.addView(webView); // 清除网页访问留下的缓存 // 由于内核缓存是全局的因此这个方法不仅仅针对webView而是针对整个应用程序 webView.clearCache(true); // 清除当前webView的访问历史记录 webView.clearHistory(); // 这个api仅仅清除自动完成填充的表单数据,并不会清除WebView存储到本地的数据 webView.clearFormData(); // https://www.panda.tv/ // https://blog.csdn.net/csdnzouqi // http://soft.imtt.qq.com/browser/tes/feedback.html // String url = "file:android_asset/index.html";// 加载主页面的url String url = "http://soft.imtt.qq.com/browser/tes/feedback.html";// 邹奇的博客主页 // 获取WebSettings对象 webSettings = webView.getSettings(); // 特别注意:5.1以上默认禁止了https和http混用。下面代码是开启 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {// 21 } webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);// 不使用缓存,直接用网络加载 webSettings.setJavaScriptEnabled(true);// webView支持javascript webSettings.setJavaScriptCanOpenWindowsAutomatically(true);// 告诉js可以自动打开window // 两者一起使用,可以让html页面加载显示适应手机的屏幕大小 webSettings.setUseWideViewPort(true); webSettings.setLoadWithOverviewMode(true); webSettings.setBuiltInZoomControls(true); //设置内置的缩放控件。若为false,则该WebView不可缩放 // webSettings.setAllowFileAccess(true); //设置可以访问文件 webSettings.setLoadsImagesAutomatically(true); //支持自动加载图片 webSettings.setDefaultTextEncodingName("utf-8");//设置编码格式 // 即允许在 File 域下执行任意 JavaScript 代码 // webSettings.setAllowFileAccess(true);// 设置是否允许 WebView 使用 File 协议 // 禁止 file 协议加载 JavaScript // if (url.startsWith("file://")){ // webSettings.setJavaScriptEnabled(false); // }else { // webSettings.setJavaScriptEnabled(true); // } webSettings.setSavePassword(false);// 关闭密码保存提醒;该方法在以后的版本中该方法将不被支持 webSettings.setDomStorageEnabled(true);// 设置支持DOM storage API // 通过addJavascriptInterface()将Java对象映射到JS对象 webView.addJavascriptInterface(new AndroidToJs(getContext()), "androidObj"); // 加载手机本地的html // webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html"); // 加载html页面的一小段内容 ('#','%', '\', '?' 分别用 %23, %25, %27, %3f 替换) // webView.loadData("显示内容", "text/html", "utf-8"); //设置WebViewClient类 webView.setWebViewClient(new WebViewClient(){ // 设置不用系统浏览器打开,直接显示在当前 webview @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // 如果不是http或者https开头的url,那么使用手机自带的浏览器打开 if (!url.startsWith("http://") && !url.startsWith("https://")){ try { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); startActivity(intent); return true; }catch (Exception e){ e.printStackTrace(); return true; } } view.loadUrl(url); return false; // return super.shouldOverrideUrlLoading(view, url); } @Override public void onReceivedSslError(WebView webView, SslErrorHandler sslErrorHandler, SslError sslError) { super.onReceivedSslError(webView, sslErrorHandler, sslError); } }); //设置WebChromeClient类 webView.setWebChromeClient(new WebChromeClient(){ @Override public void onProgressChanged(WebView view, int newProgress) { // 加载完成,隐藏进度条 if (newProgress == 100){ material_progress_bar.setVisibility(View.GONE); }else { material_progress_bar.setVisibility(View.VISIBLE); } } // // 拦截js的警告框 // // @Override // public boolean onJsAlert(WebView view, String url, String message, final JsResult result) { // ToastUtil.showToast(getContext(), "拦截js alert"); // new AlertDialog.Builder(getContext()) // .setTitle("弹框") // .setMessage(message) // .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { // @Override // public void onClick(DialogInterface dialog, int which) { // result.confirm(); // } // }) // .setCancelable(false) // .create().show(); // // return true; return super.onJsAlert(view, url, message, result); // } // // // 拦截js的确认框 // @Override // public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { // return super.onJsConfirm(view, url, message, result); // } // // // 拦截输入框 // @Override // public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { // ToastUtil.showToast(getContext(), ""+ message); // // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数) // // 假定传入进来的 url = "js://android?arg1=1&arg2=2"(同时也是约定好的需要拦截的) // Uri uri = Uri.parse(message); // // if (uri.getScheme().equals("js")){ // // if (uri.getAuthority().equals("android")){ // ToastUtil.showToast(getContext(),"Android端拦截成功!"); // // // 可以在协议上带有参数并传递到Android上 HashMap params = new HashMap(); Set collection = uri.getQueryParameterNames(); // // // 这里可以执行js所需要调用的逻辑 // // // 返回值 // result.confirm("Android端返回给js的内容"); // } // // } // // return true; return super.onJsPrompt(view, url, message, defaultValue, result); // } }); // 加载apk包中的html页面 webView.loadUrl(url); } @Override public void onDestroyView() { super.onDestroyView(); isPrepared = false;// 视图销毁的时候恢复数据加载状态 } @Override public void onHiddenChanged(boolean hidden) { super.onHiddenChanged(hidden); if (!hidden){ LogUtil.e("TAG", "onHiddenChanged1"); } } // 先让 WebView 加载null内容,然后移除 WebView,再销毁 WebView,最后置空 private void clearWebView() { if (webView != null){ webView.loadDataWithBaseURL(null, "", "text/html", "utf-8", null); webView.clearHistory(); webView.clearCache(true);// 清除缓存 ((ViewGroup)webView.getParent()).removeView(webView); webView.destroy(); webView = null; } } /** * fragment的点击事件,参数由当前fragment所依附的activity传过来 * @param keyCode * @param event * @param context */ public static void onKeyDown(int keyCode, KeyEvent event, Context context) { if (webView != null){ if (webView.canGoBack()){ webView.goBack(); }else { view_toast_exit = LayoutInflater.from(context).inflate(R.layout.view_toast_exit, null); exitApp(2000, context); } } } private static Toast toast = null;// 创建Toast对象 private static long firstTime;// 记录点击返回时第一次的时间毫秒值 private static View view_toast_exit;// 吐丝,退出应用的view /** * 退出应用 * @param timeInterval 设置第二次点击退出的时间间隔 * @param context */ private static void exitApp(long timeInterval, Context context) { if(System.currentTimeMillis() - firstTime >= timeInterval){ if (view_toast_exit != null){ toast = new Toast(context); toast.setView(view_toast_exit); toast.setGravity(Gravity.CENTER, 0, 0); toast.setDuration(Toast.LENGTH_SHORT); toast.show(); }else { ToastUtil.showToast(context, "再按一次退出程序"); } // ToastUtil.showToast(this, "再按一次退出程序"); firstTime = System.currentTimeMillis(); }else { if (toast != null){ toast.cancel(); } // finish();// 销毁当前activity System.exit(0);// 完全退出应用 } } }

那么怎么判断是否加载了x5内核呢?

webview的getX5WebViewExtension()返回非null表示已加载了x5内核webview

您的app打开网页http://soft.imtt.qq.com/browser/tes/feedback.html,显示000000表示加载的是系统内核,显示大于零的数字表示加载了x5内核(该数字是x5内核版本号)

上面我加载的url就是http://soft.imtt.qq.com/browser/tes/feedback.html,让我们运行一下看看效果:

这里写图片描述

可以看到确实是成功的加载了x5的内核。

A little bit of progress every day!Come on!



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有